package com.grapecity.forguncy;

import com.grapecity.forguncy.commands.entity.Command;
import com.grapecity.forguncy.securityprovider.ISecurityProvider;
import com.grapecity.forguncy.serverapi.entity.ForguncyApi;
import com.grapecity.forguncy.utils.AdapterClassLoader;
import com.grapecity.forguncy.utils.Utils;
import com.grapecity.forguncy.utils.ZipConfigs;
import com.grapecity.forguncy.utils.ZipPackager;

import java.io.*;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class Main {
    public static void main(String[] args) throws IOException {
        var pomBasedir = args[0];
        var type = args[1];
        switch (type) {
            case "plugin" -> packagePlugin(pomBasedir);
            case "webApi" -> packageWebApi(pomBasedir);
            case "securityProvider" -> {
                String[] securityProviderExtraFiles = new String[args.length - 2];
                System.arraycopy(args, 2, securityProviderExtraFiles, 0, args.length - 2);
                packageSecurityProvider(pomBasedir, securityProviderExtraFiles);
            }
        }
    }

    static void packageSecurityProvider(String pomBasedir, String[] securityProviderExtraFiles) throws IOException {
        File jarFile = getWithDependenceJarFile(pomBasedir);
        String jarFilePath = jarFile.getAbsolutePath();
        String jarFileName = jarFile.getName();
        List<String> names = loadJarClass(jarFilePath, MetaInfoType.SecurityProvider);
        String infoTargetFilePath = Paths
                .get(pomBasedir, "target", "com.grapecity.forguncy.securityprovider.ISecurityProvider").toString();
        Utils.writeLinesToFile(infoTargetFilePath, names.toArray(new String[0]));
        packageSecurityProviderZip(pomBasedir, jarFilePath, infoTargetFilePath, jarFileName,
                securityProviderExtraFiles);
    }

    private static void packageSecurityProviderZip(String pomBasedir, String jarFilePath, String infoTargetFilePath,
            String jarFileName, String[] securityProviderExtraFiles) {
        String sourceFolderPath = String.valueOf(Paths.get(pomBasedir, "src", "main", "resources"));
        String zipFilePath = String
                .valueOf(Paths.get(pomBasedir, "target", Utils.removeFileExtension(jarFileName) + ".zip"));

        try {
            ArrayList<ZipConfigs> files = new ArrayList<ZipConfigs>();
            files.add(new ZipConfigs(sourceFolderPath, "", true));
            files.add(new ZipConfigs(jarFilePath));
            files.add(new ZipConfigs(infoTargetFilePath));
            var absPaths = new ArrayList<String>();
            for (String arg : securityProviderExtraFiles) {
                var absPath = Paths.get(pomBasedir, arg).toAbsolutePath().toString();
                absPaths.add(absPath);
            }
            files.add(new ZipConfigs(pomBasedir, "", false, absPaths, true));
            ZipPackager.zipPaths(files.toArray(new ZipConfigs[0]), zipFilePath);
            System.out.println("ZIP file created successfully.");
        } catch (IOException e) {
            System.out.println("Error creating ZIP file: " + e.getMessage());
        }
    }

    public static void packageWebApi(String pomBasedir) throws IOException {
        File jarFile = getWithDependenceJarFile(pomBasedir);
        String jarFilePath = jarFile.getAbsolutePath();
        String jarFileName = jarFile.getName();
        List<String> names = loadJarClass(jarFilePath, MetaInfoType.WebApi);
        String infoTargetFilePath = Paths
                .get(pomBasedir, "target", "com.grapecity.forguncy.serverapi.entity.ForguncyApi").toString();
        Utils.writeLinesToFile(infoTargetFilePath, names.toArray(new String[0]));
        packageWebapiZip(pomBasedir, jarFilePath, infoTargetFilePath, jarFileName);
    }

    private static void packageWebapiZip(String pomBasedir, String jarFilePath, String infoTargetFilePath,
            String jarFileName) {
        String zipFilePath = String
                .valueOf(Paths.get(pomBasedir, "target", Utils.removeFileExtension(jarFileName) + ".fgcjwa.zip"));
        try {
            ZipConfigs[] files = new ZipConfigs[2];
            files[0] = new ZipConfigs(jarFilePath);
            files[1] = new ZipConfigs(infoTargetFilePath);
            ZipPackager.zipPaths(files, zipFilePath);
            System.out.println("ZIP file created successfully.");
        } catch (IOException e) {
            System.out.println("Error creating ZIP file: " + e.getMessage());
        }
    }

    static void packagePlugin(String pomBasedir) throws IOException {
        File jarFile = getWithDependenceJarFile(pomBasedir);
        String jarFilePath = jarFile.getAbsolutePath();
        String jarFileName = jarFile.getName();
        List<String> names = loadJarClass(jarFilePath, MetaInfoType.Plugin);
        String infoTargetFilePath = Paths.get(pomBasedir, "target", "com.grapecity.forguncy.commands.entity.Command")
                .toString();
        Utils.writeLinesToFile(infoTargetFilePath, names.toArray(new String[0]));
        packagePluginZip(jarFilePath, pomBasedir, jarFileName, infoTargetFilePath);
    }

    private static File getWithDependenceJarFile(String pomBasedir) {
        String directoryPath = Paths.get(pomBasedir, "target").toString();
        File directory = new File(directoryPath);

        File[] jarFiles = directory.listFiles((dir, name) -> name.toLowerCase().endsWith("-with-dependencies.jar"));
        assert jarFiles != null;
        if (jarFiles.length != 1) {
            throw new RuntimeException("find multiple jar file with dependence");
        }
        return jarFiles[0];
    }

    private static void packagePluginZip(String jarFilePath, String pomBasedir, String jarFileName,
            String infoTargetFilePath) {
        String resourceFolderPath = String.valueOf(Paths.get(pomBasedir, "src", "main", "resources"));
        String targetPath = String.valueOf(Paths.get(pomBasedir, "target"));
        String zipFilePath = String
                .valueOf(Paths.get(pomBasedir, "target", Utils.removeFileExtension(jarFileName) + ".zip"));
        createTempPlguinConfig(resourceFolderPath, targetPath, jarFileName);
        try {
            ZipConfigs[] files = new ZipConfigs[4];
            files[0] = new ZipConfigs(resourceFolderPath, "PluginConfig.json");
            files[1] = new ZipConfigs(jarFilePath);
            files[2] = new ZipConfigs(infoTargetFilePath);
            files[3] = new ZipConfigs(String.valueOf(Paths.get(targetPath, "PluginConfig.json")));
            ZipPackager.zipPaths(files, zipFilePath);
            System.out.println("ZIP file created successfully.");
        } catch (IOException e) {
            System.out.println("Error creating ZIP file: " + e.getMessage());
        }
    }

    private static void createTempPlguinConfig(String resourceFolderPath, String targetPath, String jarFileName) {
        var sourcePath = Paths.get(resourceFolderPath, "PluginConfig.json");
        var targetConfigPath = Paths.get(targetPath, "PluginConfig.json");
        File jsonFile = new File(String.valueOf(sourcePath));

        try {
            PluginConfigDto pluginConfigDto = Utils.getObjectMapper().readValue(jsonFile, PluginConfigDto.class);
            pluginConfigDto.jar = new String[1];
            pluginConfigDto.jar[0] = jarFileName;
            File outputFile = new File(String.valueOf(targetConfigPath));
            var objectWriter = Utils.getObjectMapper().writerWithDefaultPrettyPrinter();
            objectWriter.writeValue(outputFile, pluginConfigDto);
        } catch (Exception e) {
            System.out.println("解析JSON文件失败：" + e.getMessage());
        }
    }

    private static List<String> loadJarClass(String jarPath, MetaInfoType metaInfoType) {
        List<String> res = new ArrayList<>();
        try {
            JarFile jarFile = new JarFile(new File(jarPath));
            Enumeration<JarEntry> entries = jarFile.entries();
            URL jarUrl = new File(jarPath).toURI().toURL();
            try (AdapterClassLoader classLoader = new AdapterClassLoader(new URL[] { jarUrl }, Command.class)) {
                loadClassUsingClassLoader(metaInfoType, entries, classLoader, res);
            }
            jarFile.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return res;
    }

    private static void loadClassUsingClassLoader(MetaInfoType metaInfoType, Enumeration<JarEntry> entries,
            AdapterClassLoader classLoader, List<String> res) {
        while (entries.hasMoreElements()) {
            JarEntry entry = entries.nextElement();
            String entryName = entry.getName();
            if (entryName.endsWith(".class")) {

                String className = entryName.replace("/", ".").substring(0, entryName.length() - 6);

                String fieldName = className.substring(className.lastIndexOf(".") + 1);
                if ("module-info".equals(fieldName)) {
                    continue;// 跳过所有module-info.class
                }
                Class<?> targetClass = null;
                try {
                    targetClass = classLoader.loadClass(className);
                } catch (Exception e) {
                    System.out.println(e.getMessage());
                }
                if (isValidClass(targetClass, metaInfoType)) {
                    res.add(className);
                }
            }
        }
    }

    private static boolean isValidClass(Class<?> clazz, MetaInfoType metaInfoType) {
        if (clazz == null) {
            return false;
        }
        if (clazz.isInterface() || clazz.isEnum() || clazz.isAnnotation() || clazz.isPrimitive()) {
            return false;
        }
        int modifiers = clazz.getModifiers();
        if (!Modifier.isPublic(modifiers)) {
            return false;
        }
        return (metaInfoType == MetaInfoType.SecurityProvider && ISecurityProvider.class.isAssignableFrom(clazz)) ||
                (metaInfoType == MetaInfoType.WebApi && ForguncyApi.class.isAssignableFrom(clazz)) ||
                (metaInfoType == MetaInfoType.Plugin && isValidPluginClass(clazz));
    }

    private static boolean isValidPluginClass(Class<?> clazz) {
        boolean isAbstract = Modifier.isAbstract(clazz.getModifiers());
        return Command.class.isAssignableFrom(clazz) && !isAbstract;
    }
}